package com.hongyun.tms.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.hongyun.tms.common.PageResponseDto;
import com.hongyun.tms.common.dto.mrate.MRateQueryDto;
import com.hongyun.tms.common.dto.mrate.MRateSaveReqDto;
import com.hongyun.tms.common.enums.BizCode;
import com.hongyun.tms.common.utils.MapperingUtils;
import com.hongyun.tms.common.vo.MRateVO;
import com.hongyun.tms.entity.MRate;
import com.hongyun.tms.exception.BusinessRuntimeException;
import com.hongyun.tms.mapper.MRateMapper;
import com.hongyun.tms.service.IMRateService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 拖轮费率 服务实现类
 * </p>
 *
 * @author liPeng
 * @since 2024-06-05
 */
@Service
public class MRateServiceImpl extends ServiceImpl<MRateMapper, MRate> implements IMRateService {

    private static final Integer MAX_SHIP_LENGTH = 999;

    /**
     * 船舶费率分页
     *
     * @param rateQuery 查询参数
     * @return page
     */
    @Override
    public PageResponseDto<MRateVO> pageList(MRateQueryDto rateQuery) {
        if (rateQuery.getShipLength() != null && rateQuery.getShipLength() > MAX_SHIP_LENGTH) {
            throw new BusinessRuntimeException(BizCode.SYSTEM_ERROR, "船最大长度可输入为" + MAX_SHIP_LENGTH);
        }
        // 创建分页对象
        Page<MRate> page = new Page<>(rateQuery.getStartPage(), rateQuery.getPageSize());

        // 构建查询条件
        LambdaQueryWrapper<MRate> queryWrapper = buildQueryWrapper(rateQuery);

        // 执行分页查询
        baseMapper.selectPage(page, queryWrapper);

        // 返回分页结果
        return (PageResponseDto<MRateVO>) new PageResponseDto(page, MRateVO.class);
    }

    /**
     * 构建查询条件
     *
     * @param rateQuery 查询参数
     * @return 查询条件封装对象
     */
    private LambdaQueryWrapper<MRate> buildQueryWrapper(MRateQueryDto rateQuery) {
        return new LambdaQueryWrapper<MRate>()
                .le(rateQuery.getShipLength() != null, MRate::getLengthStart, rateQuery.getShipLength())
                .gt(rateQuery.getShipLength() != null, MRate::getLengthEnd, rateQuery.getShipLength())
                .eq(StringUtils.isNotBlank(rateQuery.getTradeType()), MRate::getTradeType, rateQuery.getTradeType())
                .like(StringUtils.isNotBlank(rateQuery.getShipKindName()), MRate::getShipKindName, rateQuery.getShipKindName())
                .orderByDesc(MRate::getTradeType)
                .orderByDesc(MRate::getUpdateTime)
                .orderByAsc(MRate::getLengthEnd);
    }

    /**
     * 保存或更新船舶费率信息
     *
     * @param dto 费率请求数据传输对象
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveAndUpdate(MRateSaveReqDto dto) {
        // 校验输入参数
        preCheck(dto);

        // 将 DTO 转换为实体对象
        MRate mRate = MapperingUtils.copyProperties(dto, MRate.class);

        mRate.setUpdateTime(LocalDateTime.now());

        if (dto.getId() == null) {
            mRate.setCreateTime(LocalDateTime.now());
            // 保存新记录
            save(mRate);
        } else {
            // 更新现有记录
            updateById(mRate);
        }
    }

    /**
     * 校验是否dto的 贸别、船舶类型、长度已在数据库中存在，若有，则不能添加这条
     *
     * @param dto 费率请求数据传输对象
     */
    private void preCheck(MRateSaveReqDto dto) {
        // 查询数据库中是否存在相同贸别和船舶类型但不同ID的记录
        List<MRate> sameList = baseMapper.selectList(new LambdaQueryWrapper<MRate>()
                .eq(MRate::getTradeType, dto.getTradeType()) // 贸别
                .eq(MRate::getShipKindCode, dto.getShipKindCode()) // 船舶类型
                .ne(dto.getId() != null, MRate::getId, dto.getId()) // update操作时忽略自己
        );

        // 筛选出长度区间重复的记录
        // 如果输入的长度范围与已有记录的长度范围有重叠，则认为是重复
        List<MRate> repeatList = sameList.stream()
                .filter(x -> isLengthOverlap(dto.getLengthStart(), dto.getLengthEnd(), x.getLengthStart(), x.getLengthEnd()))
                .collect(Collectors.toList());

        // 如果存在长度区间重复的记录，则抛出异常
        if (CollectionUtils.isNotEmpty(repeatList)) {
            throw new BusinessRuntimeException(BizCode.TUG_EXIST_SAME_LENGTH,
                    String.format("【%d-%d】的%s已存在，长度区间不能重叠",
                            repeatList.get(0).getLengthStart(), repeatList.get(0).getLengthEnd(), repeatList.get(0).getShipKindName()));
        }
    }

    /**
     * 判断两个长度区间是否有重叠
     *
     * @param dtoParamStart 前端传过来的要改动的对象的起始长度
     * @param dtoParamEnd   前端传过来的要改动的对象的结束长度
     * @param oldDataStart  数据表中已存在数据的起始长度
     * @param oldDataEnd    表中已存在数据的结束长度
     * @return 如果两个区间有重叠，则返回true，否则返回false
     */
    private boolean isLengthOverlap(int dtoParamStart, int dtoParamEnd, int oldDataStart, int oldDataEnd) {
        // 情况 1: start1 在 start2 和 end2 之间
        //     start2   end2
        //       |-------|
        //    |----|
        //   start1 end1
        return (dtoParamStart >= oldDataStart && dtoParamStart < oldDataEnd)

                // 情况 2: end1 在 start2 和 end2 之间
                //     start2   end2
                //       |-------|
                //              |----|
                //           start1 end1
                || (dtoParamEnd > oldDataStart && dtoParamEnd <= oldDataEnd)

                // 情况 3: start1 和 end1 包含 start2 和 end2
                //    start2 end2
                //       |----|
                //     |-------|
                //    start1 end1
                || (dtoParamStart < oldDataStart && dtoParamEnd > oldDataEnd);
    }

    @Override
    public List<MRateVO> export(MRateQueryDto query) {
        long currentPage = 1;
        List<MRateVO> allRecords = Lists.newArrayList();

        while (true) {
            query.setStartPage(currentPage);
            PageResponseDto<MRateVO> page = this.pageList(query);
            List<MRateVO> records = page.getRecords();
            allRecords.addAll(records);
            allRecords.forEach((rate) -> {
                rate.setLengthStr(rate.getLengthStart() + "-" + rate.getLengthEnd());
            });

            if (records.size() < query.getPageSize()) {
                // 当前页的记录数小于 pageSize，说明已经是最后一页
                break;
            }
            currentPage++;
        }

        return allRecords;
    }
}
